\documentclass[a4paper,12pt]{article}

\usepackage[margin=1.5cm]{geometry}
\usepackage{listings}
\usepackage{enumitem}

\title{Interrupt Driven Input}
\author{Chen Rushan\\chenrsster@gmail.com}
\date{2009.11.08}

\setlength{\parskip}{1.2ex}
\setlength{\parindent}{0pt}

\lstset{
    frame=single, framesep=0pt, framexleftmargin=-1pt, framextopmargin=3pt,
    framexbottommargin=3pt, aboveskip=0.5cm, belowskip=0.2cm, columns=flexible,
    basicstyle=\ttfamily
}

\begin{document}

\maketitle

The following program can implement a interrupt driven input: (It's written by a
linuxquestions.org user called \textbf{wje\_lq})

\begin{lstlisting}
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <string.h>
 #include <signal.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <sys/select.h>
 #include <time.h>
 
 volatile long long count_major_loop = 0;
 volatile long long count_signals_caught = 0;
 volatile long long count_characters_input = 0;
 volatile int flag_quit = 0;
 
 size_t
 length(const char *something)
 {
         size_t result = 0;
         while (*something++)
                 result++;
         return result;
 }
 
 void
 sig_io(int signo)
 {
         ssize_t read_count;
         char c_array[4];
         fd_set read_set;
         struct timeval timeout_val;
 
         count_signals_caught++;
         FD_ZERO(&read_set);
         FD_SET(STDIN_FILENO, &read_set);
 
         timeout_val.tv_sec = 0;
         timeout_val.tv_usec = 0;
 
         if (select(STDIN_FILENO + 1, &read_set, NULL, NULL, &timeout_val) > 0) {
                 read_count = read(STDIN_FILENO, c_array, 1);
                 if (read_count < 0) {
                         write(STDERR_FILENO, "got error\r\n",
                                         length("got error\r\n"));
                         flag_quit = 1;
                 } else if (read_count == 0) {
                         write(STDERR_FILENO, "got EOF\r\n",
                                         length("got EOF\r\n"));
                         flag_quit = 1;
                 } else if (c_array[0] == 'q') {
                         write(STDERR_FILENO, "got quit request\r\n",
                                         length("got quit request\r\n"));
                         flag_quit = 1;
                 } else {
                         count_characters_input++;
                         write(STDOUT_FILENO, "read character",
                                         length("read character"));
                         write(STDOUT_FILENO, c_array, 1);
                         write(STDOUT_FILENO, "\r\n", length("\r\n"));
                 }
         }
 }
 
 int
 main(int argc, char **argv)
 {
         struct sigaction sa;
         time_t tick_tick = 0;
 
         memset(&sa, 0, sizeof(sa));
         sa.sa_handler = sig_io;
 
         if (sigaction(SIGIO, &sa, NULL) < 0) {
                 fprintf(stderr, "[ERROR]: sigaction error\n");
                 exit(1);
         }
 
         if (fcntl(0, F_SETOWN, getpid()) < 0) {
                 fprintf(stderr, "[ERROR]: fcntl F_SETOWN error\n");
                 exit(1);
         }
 
         if (fcntl(0, F_SETFL, O_NONBLOCK | O_ASYNC) < 0) {
                 fprintf(stderr, "[ERROR]: fcntl error for O_NONBLOCK | O_ASYNC\n");
                 exit(1);
         }
 
         tick_tick = time(NULL);
 
         while (!flag_quit) {
                 if (tick_tick != time(NULL)) {
                         tick_tick = time(NULL);
                         printf("1");
                         fflush(stdout);
                         count_major_loop++;
                 }
         }
         
         printf("major loop count: %lld\r\n", count_major_loop);
         printf("signals caught: %lld\r\n", count_signals_caught);
         printf("characters input: %lld\r\n", count_characters_input);
 
         return 0;
 }
\end{lstlisting}

There are some points you need to pay attention to:

\begin{enumerate}
    \item For \verb=fcntl=, \verb=S_SETOWN= command specifies which process or
        process group will receive \verb=SIGIO= when an some specific events
        occur, and \verb=O_ASYN= for \verb=S_SETFL= command means that input or
        output becoming possible will trigger \verb=SIGIO=.

    \item Most of the standard functions cannot work safely with signal handler,
        run \verb=man 7 signal= to find the full list of all the safe functions.
        Here we use \verb=read= and \verb=write= to input and output instead of
        \verb=getchar= and \verb=printf=, since they are both unsafe functions,
        and I fell into trouble after using those two functions in
        \verb=sig_io=, besides that, we also write a function called
        \verb=length= to calculate the length of a string instead of using
        \verb=strlen= directly.

    \item To achieve the effect that the program responds as soon as one key is
        pressed, you should run this program like this:

        \begin{lstlisting}[gobble=7]
        stty raw; ./this-program; stty cooked
        \end{lstlisting}

        \verb=stty raw= is used to turn off the line-editing mode so that
        keypress will immediately be sent to program instead of waiting until
        the user presses return.
        
    \item Function \verb=select= is used, because \verb=SIGIO= will be sent even
        if you don't press a key, so you should use \verb=select= to check if
        this \verb=SIGIO= is sent due to a keypress. So what is the other event?
        In fact, when you run this program as above without redirecting, then
        {\em stdin, stdout, stderr} are actually the same device. So the other
        thing happens is that output is done. After output is done, a new event
        occurs to show that more output can be done without blocking. You can
        confirm this by counting the number of \textbf{printf/fprintf} executed
        before {\em q} is pressed and the number of key you have pressed, and
        you'll find that their sum equals to
        \textbf{count\_signal\_caught}.(Thank you so much, wje\_lq)

\end{enumerate}

\textbf{\large Troubleshooting}

\begin{enumerate}[leftmargin=3ex]
    \item Explain the behavior of the following program: (The difference
        between this one and the previous one is that there's no \verb=select=
        here and \verb=stdin= is in blocking mode).

        \begin{lstlisting}[gobble=7]
        #include <stdio.h>
        #include <stdlib.h>
        #include <errno.h>
        #include <string.h>
        #include <signal.h>
        #include <fcntl.h>
        #include <unistd.h>
        #include <sys/select.h>
        #include <time.h>
        
        volatile long long count_major_loop = 0;
        volatile long long count_signals_caught = 0;
        volatile long long count_characters_input = 0;
        volatile int flag_quit = 0;
        
        size_t
        length(const char *something)
        {
                size_t result = 0;
                while (*something++)
                        result++;
                return result;
        }
        
        void
        sig_io(int signo)
        {
                ssize_t read_count;
                char c_array[4];
                fd_set read_set;
                struct timeval timeout_val;
        
                count_signals_caught++;
                FD_ZERO(&read_set);
                FD_SET(STDIN_FILENO, &read_set);
        
                timeout_val.tv_sec = 0;
                timeout_val.tv_usec = 0;
        
                read_count = read(STDIN_FILENO, c_array, 1);
                if (read_count < 0) {
                        write(STDERR_FILENO, "got error\r\n",
                                        length("got error\r\n"));
                        flag_quit = 1;
                } else if (read_count == 0) {
                        write(STDERR_FILENO, "got EOF\r\n",
                                        length("got EOF\r\n"));
                        flag_quit = 1;
                } else if (c_array[0] == 'q') {
                        write(STDERR_FILENO, "got quit request\r\n",
                                        length("got quit request\r\n"));
                        flag_quit = 1;
                } else {
                        count_characters_input++;
                        write(STDOUT_FILENO, "read character",
                                        length("read character"));
                        write(STDOUT_FILENO, c_array, 1);
                        write(STDOUT_FILENO, "\r\n", length("\r\n"));
                }
        }
        
        int
        main(int argc, char **argv)
        {
                struct sigaction sa;
                time_t tick_tick = 0;
        
                memset(&sa, 0, sizeof(sa));
                sa.sa_handler = sig_io;
        
                if (sigaction(SIGIO, &sa, NULL) < 0) {
                        fprintf(stderr, "[ERROR]: sigaction error\n");
                        exit(1);
                }
        
                if (fcntl(0, F_SETOWN, getpid()) < 0) {
                        fprintf(stderr, "[ERROR]: fcntl F_SETOWN error\n");
                        exit(1);
                }
        
                if (fcntl(0, F_SETFL, O_ASYNC) < 0) {
                        fprintf(stderr, "[ERROR]: fcntl error for O_NONBLOCK | O_ASYNC\n");
                        exit(1);
                }
        
                tick_tick = time(NULL);
        
                while (!flag_quit) {
                        if (tick_tick != time(NULL)) {
                                tick_tick = time(NULL);
                                printf("1");
                                fflush(stdout);
                                count_major_loop++;
                        }
                }
                
                printf("major loop count: %lld\r\n", count_major_loop);
                printf("signals caught: %lld\r\n", count_signals_caught);
                printf("characters input: %lld\r\n", count_characters_input);
        
                return 0;
        }
        \end{lstlisting}

        When running this program as:
        
        \begin{lstlisting}[gobble=7]
        stty raw; ./this-program; stty cooked
        \end{lstlisting}

        You'll find that after some {\em 1} are printed, the program stops
        there, waiting for you to press a key, and then after a key is pressed,
        it prints "read character ...", and then it stops again, waiting for you
        to input again, and this process goes on and on, and never stop even if
        you press {\em q}, the only way to stop it is running command {\em
        kill}. So why?

        Actually, it's fairly easy to understand as long as you know the fact
        that \verb=SIGIO= is also sent after an output to stdout is done.
        Theoretically, after one {\em 1} is printed, the program should stop,
        but I actually see two {\em 1}, it may because some mysterious reason
        that I don't know, anyway, after {\em 1} is printed, \verb=SIGIO= is
        sent to the program, and since the read is blocking in this program, the
        program will stop, waiting for you to press a key, and after you press a
        key, a string "read character ..." is output, and this output again sent
        a signal to the program, which in turn stops the program again, so as
        long as there's output to stdout, the program will stop, and that's why
        even if a {\em q} is pressed, the program still stops instead of quiting
        immediately.

\end{enumerate}

\end{document}

